package xyz.xtt.pay.service.impl;

import static xyz.xtt.pay.utils.AssertUtils.isFalse;
import static xyz.xtt.pay.utils.AssertUtils.isNull;
import static xyz.xtt.pay.utils.AssertUtils.isTrue;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import xyz.xtt.asset.client.response.AssetAccountResponse;
import xyz.xtt.asset.client.response.AssetInfoResponse;
import xyz.xtt.asset.client.response.AssetStaticResponse;
import xyz.xtt.pay.api.dto.PayAttach;
import xyz.xtt.pay.api.enums.GoodsTypeEnum;
import xyz.xtt.pay.api.enums.PayOrderBusinessTypeEnum;
import xyz.xtt.pay.api.enums.PayOrderStatusEnum;
import xyz.xtt.pay.constants.ErrorMessage;
import xyz.xtt.pay.dto.pay.GoodsInfo;
import xyz.xtt.pay.entity.TblBusinessOrder;
import xyz.xtt.pay.entity.TblPayOrder;
import xyz.xtt.pay.entity.TblPayOrderLog;
import xyz.xtt.pay.exception.ChainException;
import xyz.xtt.pay.service.IInnerService;
import xyz.xtt.pay.service.INftOrderService;
import xyz.xtt.pay.service.IPayOrderOperator;
import xyz.xtt.pay.service.IRocketMqService;
import xyz.xtt.pay.service.ITblBusinessOrderService;
import xyz.xtt.pay.service.ITblPayOrderLogService;
import xyz.xtt.pay.service.ITblPayOrderService;
import xyz.xtt.pay.support.PayCenterHttpService;
import xyz.xtt.pay.utils.RedisLockUtil;
import xyz.xtt.pay.vo.CreateOrderRequest;
import xyz.xtt.pay.vo.PayCallbackMessage;
import xyz.xtt.pay.vo.PayCenterArgsResponse;
import xyz.xtt.user.client.response.UserInfoResponse;

/**
 * @author dale
 * @date 2025/2/25
 **/
@Component
@Slf4j
@RefreshScope
public class NftWithPhysicalPayOrderOperator implements IPayOrderOperator {
	@Autowired
	private RedisLockUtil redisLockUtil;
	@Autowired
	private ITblBusinessOrderService iTblBusinessOrderService;
	@Autowired
	private ITblPayOrderService iTblPayOrderService;
	@Autowired
	private PayCenterHttpService payCenterHttpService;
	@Autowired
	private INftOrderService nftOrderService;
	@Autowired
	private IRocketMqService rocketMqService;
	@Autowired
	private ITblPayOrderLogService iTblPayOrderLogService;
	@Autowired
	private IInnerService iInnerService;

	/**
	 * 是否支持该类型的订单
	 *
	 * @param type
	 * @return
	 */
	@Override
	public boolean canOperateType(PayOrderBusinessTypeEnum type) {
		return Objects.equals(PayOrderBusinessTypeEnum.ASSET_WITH_PHYSICAL, type);
	}

	/**
	 * 创建订单
	 *
	 * @param tblUser
	 * @param orderTypeEnum
	 * @param request
	 * @return
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public PayCenterArgsResponse createPayOrder(UserInfoResponse tblUser, PayOrderBusinessTypeEnum orderTypeEnum, CreateOrderRequest request,
			GoodsInfo goodsInfo) {
		long assetId = Long.parseLong(request.getAssetId());
		long receiveAddressId = Long.parseLong(request.getReceiveAddressId());
		String backUrl = request.getBackUrl();
		int purchaseAmount = request.getPurchaseAmount();
		long appUserId = tblUser.getAppUserId();
		checkParams(assetId, receiveAddressId, backUrl);
		AssetInfoResponse asset = iInnerService.infoWithAssetId(assetId);
		AssetAccountResponse targetAccount = iInnerService.accountInfoCheckAmount(assetId, Long.valueOf(asset.getOwnerId()));
		isNull(targetAccount, ErrorMessage.INSUFFICIENT_BALANCE);
		isNull(asset, ErrorMessage.ASSET_NOT_EXIST);
		AssetStaticResponse staticResource = iInnerService.staticInfoWithAssetId(assetId);
		isNull(staticResource, ErrorMessage.ORDER_CREATE_FAILED, () -> {
			log.error("TblAssetStaticResource is null. assetId:{}", assetId);
		});

		goodsInfo.setGoodsId(String.valueOf(assetId));
		goodsInfo.setGoodsName(asset.getAssetName());
		goodsInfo.setGoodsNums(request.getPurchaseAmount());
		goodsInfo.setGoodsType("1");

		// 分布式锁同步（商品+用户）
		String lockKey = String.format(RedisLockUtil.LOCK_KEY_USER_PURCHASE, appUserId, assetId);
		String lockValue = UUID.randomUUID().toString();
		boolean lock = false;
		try {
			lock = redisLockUtil.acquireLock(lockKey, lockValue, 1, TimeUnit.MINUTES);
			if (lock) {
				// 检查是否存在未支付完成订单，如果存在则返回现有订单
				TblBusinessOrder existOrder = iTblBusinessOrderService.queryNotPayOrder(appUserId, GoodsTypeEnum.ASSET_WITH_PHYSICAL, assetId);
				if (Objects.nonNull(existOrder)) {
					log.info("createPayOrder uid:{} find existOrder:{}", tblUser.getAppUserId(), JSONUtil.toJsonStr(existOrder));
					TblPayOrder payOrder = iTblPayOrderService.getByBusiness(orderTypeEnum, String.valueOf(existOrder.getOrderId()));
					isNull(payOrder, ErrorMessage.ORDER_CREATE_FAILED);
					log.info("createPayOrder uid:{} current payOrder:{}", tblUser.getAppUserId(), JSONUtil.toJsonStr(payOrder));
					PayCenterArgsResponse response = payCenterHttpService
							.buildCreateOrderParams(payOrder, tblUser, request.getBackUrl(), (AssetInfoResponse) null);
					response.setCreateNewOrder(false);
					goodsInfo.setGoodsPrice(existOrder.getRealPrice());
					return response;
				}

				// 创建订单
				BigDecimal unitPrice = getGoodsUnitPrice(asset);
				// 测试用户重置单价
				unitPrice = nftOrderService.testUserResetPrice(tblUser.getAppUserId(), unitPrice);

				BigDecimal totalPrice = unitPrice.multiply(new BigDecimal(purchaseAmount));
				LocalDateTime now = LocalDateTime.now();
				TblBusinessOrder businessOrder = TblBusinessOrder
						.builder()
						.orderId(IdUtil.getSnowflakeNextId())
						.appUserId(appUserId)
						.goodsType(GoodsTypeEnum.ASSET_WITH_PHYSICAL.getCode())
						.goodsId(String.valueOf(assetId))
						.goodsName(asset.getAssetName())
						.goodsImage(staticResource.getLogoUri())
						.purchaseNum(purchaseAmount)
						.originPrice(unitPrice)
						.realPrice(unitPrice)
						.totalPrice(totalPrice)
						.discountAmount(BigDecimal.ZERO)
						.payAmount(totalPrice)
						.orderStatus(PayOrderStatusEnum.UN_PAY.getCode())
						.orderTime(now)
						.receiveAddressId(receiveAddressId)
						.createTime(now)
						.updateTime(now)
						.ver(0L)
						.build();
				isFalse(iTblBusinessOrderService.save(businessOrder), ErrorMessage.ORDER_CREATE_FAILED, () -> {
					log.error("createOrder failed. save businessOrder failed. uid:{} param:{}", tblUser.getAppUserId(), JSONUtil.toJsonStr(request));
				});
				goodsInfo.setGoodsPrice(unitPrice);
				// 创建支付订单
				PayAttach payAttach = PayAttach.builder().goodsId(asset.getGoodsId()).assetId(assetId).purchaseNum(purchaseAmount).build();
				TblPayOrder payOrder = nftOrderService
						.saveUnPayOrder(orderTypeEnum, String.valueOf(businessOrder.getOrderId()), totalPrice, appUserId, JSONUtil.toJsonStr(payAttach));

				// 插入延时取消任务
				nftOrderService.sendPayDelayMessage(payOrder);
				return payCenterHttpService.buildCreateOrderParams(payOrder, tblUser, request.getBackUrl(), (AssetInfoResponse) null);
			} else {
				log.warn("createOrder failed. acquire lock on goods and user failed. uid:{} param:{}", tblUser.getAppUserId(), JSONUtil.toJsonStr(request));
			}
		} finally {
			if (lock) {
				redisLockUtil.releaseLock(lockKey, lockValue);
			}
		}
		throw new ChainException(ErrorMessage.UNKNOWN_ERROR);
	}

	private BigDecimal getGoodsUnitPrice(AssetInfoResponse asset) {
		return asset.getPrice();
	}

	/**
	 * 确认支付订单
	 *
	 * @param payOrder
	 * @param callbackMessage
	 * @param appUserId
	 * @return
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean confirmPayOrder(TblPayOrder payOrder, PayCallbackMessage callbackMessage, String appUserId) {
		boolean needRefund = false;
		TblBusinessOrder tblBusinessOrder = iTblBusinessOrderService.getById(Long.valueOf(payOrder.getBusinessRefId()));
		isNull(tblBusinessOrder, ErrorMessage.ORDER_OPERATE_FAILED, () -> log.error("支付中心支付成功，没有找到对应的业务订单 {}", JSONUtil.toJsonStr(callbackMessage)));
		if (Objects.equals(tblBusinessOrder.getOrderStatus(), PayOrderStatusEnum.UN_PAY.getCode())) {
			try {
				isFalse(iTblBusinessOrderService.updatePaySuccess(tblBusinessOrder), ErrorMessage.ORDER_OPERATE_FAILED, () -> log
						.error("支付中心支付成功，业务订单确认失败 tblBusinessOrder:{} callbackMessage:{}", JSONUtil.toJsonStr(tblBusinessOrder), JSONUtil
								.toJsonStr(callbackMessage)));
				// 记录支付成功
				nftOrderService.savePaySuccessRecord(payOrder, callbackMessage);

				// 发送支付成功消息
				rocketMqService.sendPayOrderStatusChangeMsg(payOrder.getPayOrderId());
				return true;
			} catch (ChainException e) {
				log.error("支付中心支付成功，业务订单确认失败 {}", JSONUtil.toJsonStr(callbackMessage), e);
				// 处理失败，取消订单
				doPayCancel(payOrder, "支付中心支付成功，业务订单确认失败. exp:" + e.getMsg() + " callbackMessage:" + JSONUtil.toJsonStr(callbackMessage));
				needRefund = true;
			}
		} else if (Objects.equals(payOrder.getPayStatus(), PayOrderStatusEnum.PAY_CANCEL.getCode())) {
			if (nftOrderService.alreadyApplyRefund(payOrder.getPayOrderId())) {
				log.warn("支付中心支付成功，业务订单已取消，已申请退款 {}", JSONUtil.toJsonStr(callbackMessage));
			} else {
				// 业务订单已取消，向支付中心申请退款
				needRefund = true;
			}
		} else {
			log.warn("支付处理失败，目标订单不是待支付状态！ {}", JSONUtil.toJsonStr(callbackMessage));
		}

		if (needRefund) {
			nftOrderService.startRefundOrder(payOrder, callbackMessage);
		}
		return false;
	}

	/**
	 * 取消支付订单
	 *
	 * @param payOrder
	 * @param reason
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void doPayCancel(TblPayOrder payOrder, String reason) {
		// 取消关联业务交易
		TblBusinessOrder businessOrder = iTblBusinessOrderService.getById(Long.valueOf(payOrder.getBusinessRefId()));
		isFalse(iTblBusinessOrderService.updatePayCancel(businessOrder, reason), ErrorMessage.ORDER_OPERATE_FAILED, () -> log
				.error("支付中心支付失败，业务订单确认失败 tblBusinessOrder:{} reason:{}", JSONUtil.toJsonStr(businessOrder), reason));

		// 取消payOrder
		TblPayOrderLog payOrderLog = TblPayOrderLog
				.builder()
				.payOrderLogId(IdUtil.getSnowflakeNextId())
				.payStatus(PayOrderStatusEnum.PAY_CANCEL.getCode())
				.payOrderId(payOrder.getPayOrderId())
				.createTime(System.currentTimeMillis())
				.detail(reason)
				.build();
		isFalse(iTblPayOrderLogService.save(payOrderLog), ErrorMessage.ORDER_OPERATE_FAILED);

		isFalse(iTblPayOrderService
				.updatePayStatus(payOrder.getPayOrderId(), PayOrderStatusEnum.UN_PAY, PayOrderStatusEnum.PAY_CANCEL), ErrorMessage.ORDER_OPERATE_FAILED);

		// 发送支付订单状态变更消息
		rocketMqService.sendPayOrderStatusChangeMsg(payOrder.getPayOrderId());
	}

	@Override
	public GoodsInfo getGoodsInfo(TblPayOrder payOrder) {
		TblBusinessOrder tblBusinessOrder = iTblBusinessOrderService.getById(Long.valueOf(payOrder.getBusinessRefId()));
		GoodsInfo goodsInfo = new GoodsInfo();
		goodsInfo.setGoodsId(tblBusinessOrder.getGoodsId());
		goodsInfo.setGoodsName(tblBusinessOrder.getGoodsName());
		goodsInfo.setGoodsNums(tblBusinessOrder.getPurchaseNum());
		goodsInfo.setGoodsPrice(tblBusinessOrder.getRealPrice().setScale(2, RoundingMode.HALF_UP));
		goodsInfo.setGoodsType("1");
		return goodsInfo;
	}

	private void checkParams(long assetId, long receiveAddressId, String backUrl) {
		isTrue(assetId <= 0, ErrorMessage.ARGUMENT_NOT_VALID, () -> {
			log.error("assetId is null");
		});
		isTrue(receiveAddressId <= 0, ErrorMessage.ARGUMENT_NOT_VALID, () -> {
			log.error("receiveAddressId is null");
		});
		isTrue(StringUtils.isBlank(backUrl), ErrorMessage.ARGUMENT_NOT_VALID, () -> {
			log.error("backUrl is null");
		});
	}
}
